package com.smsc.headend.task.engine.service.collection.impl;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.SystemClock;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.json.JSONUtil;
import com.google.common.base.Preconditions;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.smsc.headend.common.enums.PaymentSwithPreToPostOptGroupEnum;
import com.smsc.headend.common.enums.errorcode.TaskExecuteErrorCode;
import com.smsc.headend.common.utils.RedisKeys;
import com.smsc.headend.common.utils.RedisUtils;
import com.smsc.headend.module.asset.entity.Meter;
import com.smsc.headend.module.meterscheme.entity.CollMeterTaskPlan;
import com.smsc.headend.module.report.entity.LogModeSwitchToPostHisSub;
import com.smsc.headend.module.task.dto.AtomicTask;
import com.smsc.headend.module.task.dto.Task;
import com.smsc.headend.module.task.enums.SubTaskStatus;
import com.smsc.headend.module.task.enums.TaskType;
import com.smsc.headend.task.engine.dao.TaskDAO;
import com.smsc.headend.module.task.dto.AtomicTaskState;
import com.smsc.headend.task.engine.exception.UDISTaskExecutorException;
import com.smsc.headend.task.engine.service.CollMeterTaskPlanService;
import com.smsc.headend.task.engine.service.FeignAssetManagementService;
import com.smsc.headend.task.engine.service.asset.AssetService;
import com.smsc.headend.task.engine.service.collection.CollectTaskService;
import com.smsc.headend.task.engine.service.collection.RecollectTaskService;
import com.smsc.headend.task.engine.service.task.TaskService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class CollectTaskServiceImpl implements CollectTaskService {

    public static final String PROFILE_START_TIME = "profileStartTime";
    public static final String PROFILE_END_TIME = "profileEndTime";
    public static final String PROFILE_TYPE = "profileType";
    public static final String COLL_TASK_TIME_SPAN = "COLL_TASK_TIME_SPAN";
    public static final String DATA_ITEM_ID = "dataItemId";
    @Autowired
    RedisUtils redisUtils;
    @Autowired
    TaskDAO taskDAO;

    @Autowired
    TaskService taskService;
    @Autowired
    AssetService assetService;

    @Autowired
    CollMeterTaskPlanService collMeterTaskPlanService;

    @Autowired
    RecollectTaskService recollectTaskService;
    @Autowired
    FeignAssetManagementService assetManagementRemoteService;

    Cache<String, Set<Long>> lpDataCache = CacheBuilder.newBuilder()
            //设置缓存容器的初始容量为10
            .initialCapacity(100)
            // maximumSize 设置缓存大小
            //设置并发级别为20，并发级别是指可以同时写缓存的线程数
            .concurrencyLevel(20)
            // expireAfterWrite设置写缓存15分钟过期
            .expireAfterWrite(15, TimeUnit.MINUTES)
            .build();

    @Override
    public void saveLpTv(String taskNo, Set<Long> timeValues) {
        Set<Long> tvs = lpDataCache.getIfPresent(taskNo);
        if (CollectionUtils.isEmpty(tvs)) {
            tvs = new HashSet<>();
        }

        tvs.addAll(timeValues);
        lpDataCache.put(taskNo, tvs);
    }

    @Override
    public Set<Long> getAndInvalidLpTv(String taskNo) {
        Set<Long> ifPresent = lpDataCache.getIfPresent(taskNo);
        if (CollectionUtils.isEmpty(ifPresent)) {
            lpDataCache.invalidate(taskNo);
        }
        return ifPresent;
    }

    public String getCollectionTaskLpDataItemId(Task collectionTask) {
        String atomicTaskParam = collectionTask.getSubTaskGroups().get(0).getSubTasks().get(0).getAtomicTaskParam();
        if (StringUtils.isEmpty(atomicTaskParam)) {
            return null;
        }
        Map<String, String> paramMap = JSONUtil.toBean(atomicTaskParam, Map.class);
        return paramMap.get(DATA_ITEM_ID);
    }

    @Override
    public void delMeterCollectTask(Long comId, Long meterId, String taskNo, String lpDataItemId) {
        if (comId == null || meterId == null || lpDataItemId == null) {
            log.error("del CollectTask: comId={}, meterId={}, lpDataItemId={}, taskNo={}", comId, meterId, taskNo, lpDataItemId);
            return;
        }
//        String key = RedisKeys.getMeterCollectTaskKey(comId, meterId, lpDataItemId);
//        redisUtils.del(key);
    }

    @Override
    public Map<String, Object> scheduleTaskTimeInit(Task task, Map<String, Object> paramMap, Meter meter, String taskType) throws UDISTaskExecutorException {
        if (!taskType.equalsIgnoreCase(TaskType.ScheduleRead_Profile.toString())) {
            return paramMap;
        }
        if (paramMap.containsKey(PROFILE_START_TIME) && paramMap.containsKey(PROFILE_END_TIME)) {
            return paramMap;
        }
        Long profileId = MapUtil.getLong(paramMap, "dataItemId");
        Integer interval = MapUtil.getInt(paramMap, "interval");
        String profileType = MapUtil.getStr(paramMap, PROFILE_TYPE);
        CollMeterTaskPlan collMeterTaskPlan = collMeterTaskPlanService.getCollMeterTaskPlan(meter.getMeterId(), profileId);
        if (isLatestCollectComplete(collMeterTaskPlan, paramMap)) {
            throw new UDISTaskExecutorException(TaskExecuteErrorCode.READ_PROFILE_START_TIME_EARLIER_THAN_PROGRESS.toString());
        }
        Long startTime = getScheduleReadStartTime(collMeterTaskPlan, profileType, meter);
        if (taskType.equalsIgnoreCase(TaskType.ScheduleRead_Profile.toString())) {
            //Only Schedule Reading need recollect
            startTime = checkAndUpdateForRecollect(startTime, profileId, interval, profileType, meter);
        }
        Long endTime = getScheduleReadEndTime(profileType, taskType);
        startTime += 60; //增加60s 防止定抄采集上次重复数据点
        paramMap.put(PROFILE_START_TIME, Convert.toStr(startTime));
        paramMap.put(PROFILE_END_TIME, Convert.toStr(endTime));
        task.setParamJson(JSONUtil.toJsonStr(paramMap));
        taskService.updateTask(task);
        return paramMap;
    }

    @Override
    public Boolean isLatestCollectComplete(CollMeterTaskPlan collMeterTaskPlan, Map<String, Object> paramMap) {
        if (collMeterTaskPlan == null || collMeterTaskPlan.getProgressTime() == null) {
            return false;
        }
        Integer interval = MapUtil.getInt(paramMap, "interval");
        String profileType = MapUtil.getStr(paramMap, PROFILE_TYPE);
        if ("Minutely".equalsIgnoreCase(profileType)) {
            if ((collMeterTaskPlan.getProgressTime()) + interval > SystemClock.now() / 1000) {
                return true;
            }
        }

        if ("Daily".equalsIgnoreCase(profileType)) {
            Date today = DateUtils.truncate(new Date(), Calendar.DAY_OF_MONTH);
            if (today.getTime() / 1000 == collMeterTaskPlan.getProgressTime().intValue()) {
                return true;
            }
        }

        if ("Monthly".equalsIgnoreCase(profileType)) {
            Date currentMon = DateUtils.truncate(new Date(), Calendar.MONTH);
            if (currentMon.getTime() / 1000 == collMeterTaskPlan.getProgressTime().intValue()) {
                return true;
            }
        }
        return false;
    }

    private Long getScheduleReadEndTime(String profileType, String taskType) {
        Long now = SystemClock.now() / 1000;
        if (!taskType.equalsIgnoreCase(TaskType.ScheduleRead_Profile.toString())) {
            return now;
        }
        if ("Daily".equalsIgnoreCase(profileType)) {
            return DateUtils.truncate(new Date(), Calendar.DATE).getTime() / 1000;
        }
        if ("Monthly".equalsIgnoreCase(profileType)) {
            return DateUtils.truncate(new Date(), Calendar.MONTH).getTime() / 1000;
        }
        return now;
    }

    private Long checkAndUpdateForRecollect(Long startTime, Long profileId, Integer interval, String profileType, Meter meter) {
        String taskGenerateEnable = assetService.getSystemProperty("RECOLL_TASK_GENERATE");
        if (!"True".equalsIgnoreCase(taskGenerateEnable) || interval == null) {
            return startTime;
        }

        if ("Minutely".equalsIgnoreCase(profileType)) {
            return minuteProfileProgressUpdate(startTime, interval, meter.getMeterId(), profileId);
        }

        if ("Daily".equalsIgnoreCase(profileType)) {
            return dayProfileProgressUpdate(startTime, interval, meter.getMeterId(), profileId);
        }

        if ("Monthly".equalsIgnoreCase(profileType)) {
            return monthlyProfileProgressUpdate(startTime, interval, meter.getMeterId(), profileId);
        }

        log.error("Illegal profile type for schedule Reading, {}, meterId:{}", profileType, meter.getMeterId());
        return null;
    }


    private Long minuteProfileProgressUpdate(Long startTime, Integer interval, Long meterId, Long profileId) {
        Long result = startTime;
        String updateLimit = assetService.getSystemProperty("COLL_UPDATE_PROGRESS_TIME"); //default 9 hour
        Integer updateHour = 9;
        if (org.apache.commons.lang.StringUtils.isNotEmpty(updateLimit)) {
            try {
                updateHour = Convert.toInt(updateLimit);
            } catch (Exception e) {
                log.error("minute updateLimit convert failed, {}", updateLimit);
            }
        }
        long currentSecond = SystemClock.now() / 1000;
        Long updateThreshold = currentSecond - updateHour * 60 * 60;

        updateThreshold = getRoundedEndTime(updateThreshold, interval);
        //进度时间 到 强制更新时间大于2小时
        if (startTime < updateThreshold) {
            //进度应该更新到两小时前
            long progressTime = getRoundedEndTime(currentSecond - 2 * 60 * 60, interval);
            result = progressTime; //设置当前这次定抄任务的开始时间(2小时前)
//            collMeterTaskPlanService.saveMeterTaskPlan(meterId, this.templateLoadProfile.getLpDataItemId(), progressTime);
            log.info("generating collect: auto recollect gen, meterId={}, startTime:{},taskPlanTime:{}", meterId, startTime, progressTime);
            recollectTaskService.generateRecollectTask(meterId, startTime, progressTime, profileId, interval);
            collMeterTaskPlanService.saveMeterCollectProgress(progressTime, meterId, profileId);
        }
        return result;
    }

    private Long monthlyProfileProgressUpdate(Long startTime, Integer interval, Long meterId, Long profileId) {
        Long result = startTime;
        Date now = DateUtils.truncate(new Date(), Calendar.MONTH);
        Preconditions.checkNotNull(startTime);
        //进度时间 更新到上个月
        Long endTime = DateUtils.addMonths(now, -1).getTime() / 1000;
        log.debug("month profile startTime: start={}, end={}", startTime, endTime);
        if (startTime < endTime) {
            result = endTime; //设置当前这次定抄任务的开始时间

            log.info("generating collect: auto recollect month gen, meterId={}, startTime:{},taskPlanTime:{}", meterId, startTime, endTime);
            recollectTaskService.generateRecollectTask(meterId, startTime, endTime, profileId, interval);
            collMeterTaskPlanService.saveMeterCollectProgress(endTime, meterId, profileId);
        }
        return result;
    }

    private Long dayProfileProgressUpdate(Long startTime, Integer interval, Long meterId, Long profileId) {
        Long result = startTime;
        String updateLimit = assetService.getSystemProperty("COLL_UPDATE_PROGRESS_DAYS"); //default 2 day
        Integer updateDay = 2;
        if (!org.apache.commons.lang.StringUtils.isEmpty(updateLimit)) {
            updateDay = Convert.toInt(updateLimit);
        }
        Long endTime = DateUtils.truncate(new Date(), Calendar.DAY_OF_MONTH).getTime() / 1000 - Convert.toLong(updateDay) * 24 * 60 * 60;
        //进度时间 更新到当前时间前一天
        if (startTime < endTime - 24 * 60 * 60) {
            result = endTime; //设置当前这次定抄任务的开始时间
            log.info("generating collect: auto recollect day gen, meterId={}, startTime:{},taskPlanTime:{}", meterId, startTime, endTime);
            recollectTaskService.generateRecollectTask(meterId, startTime, endTime, profileId, interval);
            collMeterTaskPlanService.saveMeterCollectProgress(endTime, meterId, profileId);
        }
        return result;
    }

    private Long getRoundedEndTime(Long endTime, Integer interval) {
        Date dayStart = DateUtils.truncate(new Date(endTime * DateUtils.MILLIS_PER_SECOND), Calendar.DATE);
        long t = dayStart.getTime() / DateUtils.MILLIS_PER_SECOND;
        while (t < endTime) {
            t += interval;
        }
        return t;
    }

    /**
     * Get ScheduleRead Start Time
     *
     * @param collMeterTaskPlan
     * @param profileType       enum {"Minutely", "Daily" , "Monthly"}
     * @param meter
     * @return
     */
    private Long getScheduleReadStartTime(CollMeterTaskPlan collMeterTaskPlan, String profileType, Meter meter) {
        if (collMeterTaskPlan != null) {
            return collMeterTaskPlan.getProgressTime();
        }
        Long startTime = meter.getInstallDate() != null ? meter.getInstallDate() : 0L;
        Long systemDefaultStartTime = null;
        if (!"Monthly".equalsIgnoreCase(profileType)) {
            String collOldestStartDay = assetService.getSystemProperty("COLL_OLDEST_START_DAY");
            Integer systemOldestStartDay = 10;
            try {
                systemOldestStartDay = Convert.toInt(collOldestStartDay);
            } catch (Exception e) {
                log.debug("system set COLL_OLDEST_START_DAY error");
            }
            systemDefaultStartTime = SystemClock.now() / 1000 - Convert.toLong(systemOldestStartDay) * 24 * 60 * 60;
        } else {
            systemDefaultStartTime = SystemClock.now() / 1000 - 365 * 24 * 60 * 60;
        }
        return NumberUtil.max(systemDefaultStartTime, startTime);
    }

    @Override
    public Date[] getProfileStartEndDate(Task task, AtomicTask atomicTask, Map<String, Object> param, AtomicTaskState state) {
        Date startDate = null;
        Date endDate = null;
        if (!TaskType.ScheduleRead_Profile.toString().equalsIgnoreCase(task.getTaskType())) {
            if (TaskType.SwitchMeterPaymentPreToPost.toString().equalsIgnoreCase(task.getTaskType())) {
                //付费模式切换需要抄读曲线冻结的前半小时至后一小时时间段之内冻结的曲线
                Long taskId = MapUtil.getLong(param, "taskId");
                LogModeSwitchToPostHisSub hisSub = assetManagementRemoteService.getStepRecordToPost(taskId,task.getMeterId(), PaymentSwithPreToPostOptGroupEnum.FROZEN_DAILY_BILLING_PROFILE.getOperationGroup().longValue());
                Long captureTime = null;
                if(null != hisSub && hisSub.getStatus()== SubTaskStatus.Success.getCode()) {
                    captureTime = hisSub.getTvStart() * 1000;
                }
                if(null == captureTime) {
                    captureTime = System.currentTimeMillis();
                }
                startDate = new Date(captureTime - 30 * 60 * 1000L);
                endDate = new Date(captureTime + 60 * 60 * 1000L);
            } else {
                if (!param.containsKey(PROFILE_START_TIME)) {
                    startDate = DateUtils.addDays(new Date(), -1);
                } else {
                    startDate = new Date(Long.parseLong(String.valueOf(param.get(PROFILE_START_TIME))) * 1000);
                }
                if (!param.containsKey(PROFILE_END_TIME)) {
                    endDate = new Date();
                } else {
                    endDate = new Date(Long.parseLong(String.valueOf(param.get(PROFILE_END_TIME))) * 1000);
                }
            }
        }
        //定抄任务保存拆分时间段
        else {
            String profileType = param.containsKey(PROFILE_TYPE) ? String.valueOf(param.get(PROFILE_TYPE)) : null;
            Long startTv = Long.parseLong(String.valueOf(param.get(PROFILE_START_TIME)));
            Date finalEndDate = new Date(Long.parseLong(String.valueOf(param.get(PROFILE_END_TIME))) * 1000);
            if ("Minutely".equalsIgnoreCase(profileType)) {
                if (state != null) {
                    startTv = state.getCollectedEndTv();
                }
                startDate = new Date(startTv * 1000);
                String span = assetService.getSystemProperty(COLL_TASK_TIME_SPAN);
                Integer spanTime = 8;
                try {
                    spanTime = Convert.toInt(span);
                } catch (Exception e) {
                    log.debug("COLL_TASK_TIME_SPAN error {}", span);
                }
                endDate = DateUtils.addHours(startDate, spanTime);
                if (endDate.after(finalEndDate)) {
                    endDate = finalEndDate;
                }
            } else {
                startDate = new Date(startTv * 1000);
                endDate = finalEndDate;
            }
        }
        Date[] result = new Date[2];
        result[0] = startDate;
        result[1] = endDate;
        return result;
    }
}
